package com.biz.primus.ms.payment.service;

import com.biz.primus.base.exception.BizSilentException;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.base.vo.LongIdVo;
import com.biz.primus.model.payment.enums.PaymentExceptionEnum;
import com.biz.primus.model.payment.vo.req.PaymentOrderReqVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.payment.dao.po.PaymentOrder;
import com.biz.primus.ms.payment.dao.repository.PaymentOrderRepository;
import com.biz.primus.ms.payment.event.NewPaymentOrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;

import javax.transaction.Transactional;
import java.sql.Timestamp;
import java.util.List;

import static com.biz.primus.model.payment.enums.PaymentExceptionEnum.ILLEGAL_PARAMETER;
import static com.biz.primus.model.payment.enums.PaymentExceptionEnum.PAYMENT_ORDER_NOT_FOUND;
import static java.lang.String.format;

/**
 * Created by defei on 9/26/17.
 */
@Slf4j
@Service
public class PaymentOrderService extends AbstractBaseService {

    private final PaymentOrderRepository repository;

    @Autowired
    public PaymentOrderService(PaymentOrderRepository repository) {
        this.repository = repository;
    }

    /**
     * 为商户指定订单创建一个新的支付单。
     */
    @Transactional
    public PaymentOrder create(PaymentOrderReqVo reqVo) {

        AssertUtils.notNull(reqVo, ILLEGAL_PARAMETER, "Argument reqVo is null.");

        PaymentOrder target = new PaymentOrder();
        BeanUtils.copyProperties(reqVo, target);
        target.setMchId(reqVo.getMchId().toString());
        target.setId(idService.getNextId());
        return repository.save(target);
    }

    /**
     * 为支付单绑定第三方交易单号
     *
     * @param paymentOrderId 支付单号
     * @param tradeNo        第三方交易单号
     */
    @Transactional
    public void bindTradeNo(Long paymentOrderId, String tradeNo) {

        AssertUtils.notNull(paymentOrderId, ILLEGAL_PARAMETER, "Argument reqVo is null.");
        AssertUtils.hasText(tradeNo, ILLEGAL_PARAMETER, "Argument reqVo is null.");

        repository.bindTradeNo(paymentOrderId, tradeNo);
    }

    /**
     * 作废所有未支付订单
     *
     * @param mchId      商户id
     * @param mchOrderNo 商户订单id
     */
    @Transactional
    public void discardAllPaymentOrderIfNotPaid(String mchId, String mchOrderNo) {

        repository.makeExistPaymentOrderToDiscardIfNotPaid(mchId, mchOrderNo);
    }

    List<PaymentOrder> findPaymentOrder(String mchId, String mchOrderNo) {

        AssertUtils.notNull(mchId, ILLEGAL_PARAMETER, "Argument mchId is null.");
        AssertUtils.notNull(mchOrderNo, ILLEGAL_PARAMETER, "Argument mchOrderNo is null.");
        return repository.findByMchIdAndMchOrderNoOrderByCreateTimestampDesc(mchId, mchOrderNo);
    }

    PaymentOrder findLatestPaymentOrder(String mchId, String mchOrderNo) {

        List<PaymentOrder> paymentOrders = findPaymentOrder(mchId, mchOrderNo);
        AssertUtils.notEmpty(paymentOrders, PAYMENT_ORDER_NOT_FOUND, format("PaymentOrder not found by mchId:[%s] and mchOrderNo:[%s]", mchId, mchOrderNo));
        return paymentOrders.get(0);
    }

    /**
     * 查询指定订单号的支付单
     *
     * @param orderCode 订单号
     * @return PaymentOrder {@link PaymentOrder}
     */
    public PaymentOrder findLatestPaymentOrder(String orderCode) {
        List<PaymentOrder> paymentOrders = repository.findByMchOrderNoOrderByCreateTimestampDesc(orderCode);
        AssertUtils.notEmpty(paymentOrders, PAYMENT_ORDER_NOT_FOUND, format("PaymentOrder not found by orderCode:[%s]", orderCode));
        return paymentOrders.get(0);
    }

    /**
     * 更新支付状态为已经付款
     *
     * @param paymentOrderId 支付单号
     * @param tradeNo
     */
    @Transactional
    public void markPaymentOrderPaymentStatusToPaid(Long paymentOrderId, String tradeNo, Timestamp paySuccessTime) {

        Integer affectedRow = repository.confirmPaid(paymentOrderId, tradeNo, paySuccessTime);
        if (affectedRow == 0) {
            throw new BizSilentException(PaymentExceptionEnum.PAYMENT_ORDER_IS_NOT_PAYABLE, "Order status is not not support to pay.");
        }
    }

    /**
     * 更新支付状态为退款中
     */
    @Transactional
    public void markPaymentOrderPaymentStatusToRefunding(Long id, String refundNo, Timestamp timestamp) {

        Integer affectedRow = repository.confirmRefunding(id, refundNo, timestamp);
        if (affectedRow == 0) {
            throw new BizSilentException(PaymentExceptionEnum.PAYMENT_ORDER_IS_NOT_REFUNDABLE, "Order status is not not support to refund.");
        }
    }

    /**
     * 更新支付状态为已退款
     */
    @Transactional
    public void markPaymentOrderPaymentStatusToRefunded(Long id, String refundNo, Timestamp timestamp) {

        Integer affectedRow = repository.confirmRefunded(id, refundNo, timestamp);
        if (affectedRow == 0) {
            throw new BizSilentException(PaymentExceptionEnum.PAYMENT_ORDER_IS_NOT_REFUNDABLE, "Order status is not not support to refund.");
        }
    }

    /**
     * 查找 PaymentOrder
     *
     * @param reqVo {@link LongIdVo}
     * @throws BizSilentException {@link PaymentExceptionEnum#PAYMENT_ORDER_NOT_FOUND}
     */
    PaymentOrder findOne(LongIdVo reqVo) throws BizSilentException {

        AssertUtils.notNull(reqVo, ILLEGAL_PARAMETER, "Argument reqVo is null.");
        AssertUtils.notNull(reqVo.getId(), ILLEGAL_PARAMETER, "Argument reqVo.id null.");

        PaymentOrder paymentOrder = repository.findOne(reqVo.getId());
        AssertUtils.notNull(paymentOrder, ILLEGAL_PARAMETER, format("PaymentOrder not found by id:[%s]", reqVo.getId()));
        return paymentOrder;
    }

    //TODO
    //    public SpringPageVO<PaymentOrderResVo> findAll(PaymentOrderQueryVo queryVo) {
    //
    //        PageRequest pageable = queryVo.toPageRequest();
    //        Page<PaymentOrder> page = repository.findAll(pageable);
    //        return SpringPageToSpringPageVO.of(page, input -> CopyUtils.copyProperties(input, new PaymentOrderResVo()));
    //    }

    /**
     * 事务提交成功之后才触发的事件监听器:@TransactionalEventListener
     */
    @TransactionalEventListener
    void listenNewCatEvent(NewPaymentOrderEvent event) {
        log.info("listen event: {}", event);
    }

    public void update(PaymentOrder paymentOrder) {
        AssertUtils.notNull(paymentOrder, ILLEGAL_PARAMETER, "支付单不存在");
        repository.save(paymentOrder);
    }
}
